#include <stdbool.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>


/* vsprintf.c -- Lars Wirzenius & Linus Torvalds. */
/*
 * Wirzenius wrote this portably, Torvalds fucked it up :-)
 */

#include <stdarg.h>  
#include <string.h>  

/* we use this so that we can do without the ctype library */
#define is_digit(c) ((c) >= '0' && (c) <= '9')  

static int skip_atoi(const char **s)
{
	int i = 0;

	while (is_digit(**s))
		i = i * 10 + *((*s)++) - '0';
	return i;
}

#define ZEROPAD 1       /* pad with zero */  
#define SIGN    2       /* unsigned/signed long */  
#define PLUS    4       /* show plus */  
#define SPACE   8       /* space if plus */  
#define LEFT    16      /* left justified */  
#define SPECIAL 32      /* 0x */  
#define SMALL   64      /* use 'abcdef' instead of 'ABCDEF' */  

#define do_div(n,base) ({ \
int __res; \
__asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base)); \
__res; })

static char* number(char* str, int num, int base, int size, int precision, int type)
{
	char c, sign, tmp[36];
	const char *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	int i;

	if (type & SMALL)
	{
		digits = "0123456789abcdefghijklmnopqrstuvwxyz";
	}

	if (type & LEFT)
	{
		type &= ~ZEROPAD;
	}

	if (base < 2 || base > 36)
	{
		return 0;
	}

	c = (type & ZEROPAD) ? '0' : ' ';

	if (type & SIGN && num < 0)
	{
		sign = '-';
		num = -num;
	}
	else
	{
		sign = (type&PLUS) ? '+' : ((type&SPACE) ? ' ' : 0);
	}

	if (sign)
	{
		size--;
	}

	if (type&SPECIAL)
	{
		if (base == 16)
		{
			size -= 2;
		}
		else if (base == 8)
		{
			size--;
		}
	}

	i = 0;

	if (num == 0)
	{
		tmp[i++] = '0';
	}
	else
	{
		while (num != 0)
		{
			tmp[i++] = digits[do_div(num, base)];
		}
	}

	if (i > precision)
	{
		precision = i;
	}

	size -= precision;

	if (!(type&(ZEROPAD + LEFT)))
	{
		while (size-- > 0)
		{
			*str++ = ' ';
		}
	}

	if (sign)
	{
		*str++ = sign;
	}

	if (type&SPECIAL)
	{
		if (base == 8)
		{
			*str++ = '0';
		}
		else if (base == 16)
		{
			*str++ = '0';
			*str++ = digits[33];
		}
	}

	if (!(type&LEFT))
	{
		while (size-- > 0)
		{
			*str++ = c;
		}
	}

	while (i < precision--)
	{
		*str++ = '0';
	}

	while (i-- > 0)
	{
		*str++ = tmp[i];
	}

	while (size-- > 0)
	{
		*str++ = ' ';
	}
	return str;
}

int vsprintf(char *buf, const char *fmt, va_list args)
{
	int len;
	int i;
	char * str;
	char *s;
	int *ip;

	int flags;      /* flags to number() */

	int field_width;    /* width of output field */
	int precision;      /* min. # of digits for integers; max
				   number of chars for from string */
	//int qualifier;      /* 'h', 'l', or 'L' for integer fields */

	for (str = buf; *fmt; ++fmt) {
		if (*fmt != '%') {
			*str++ = *fmt;
			continue;
		}

		/* process flags */
		flags = 0;
	repeat:
		++fmt;      /* this also skips first '%' */
		switch (*fmt) {
		case '-': flags |= LEFT; goto repeat;
		case '+': flags |= PLUS; goto repeat;
		case ' ': flags |= SPACE; goto repeat;
		case '#': flags |= SPECIAL; goto repeat;
		case '0': flags |= ZEROPAD; goto repeat;
		}

		/* get field width */
		field_width = -1;
		if (is_digit(*fmt))
			field_width = skip_atoi(&fmt);
		else if (*fmt == '*') {
			/* it's the next argument */
			field_width = va_arg(args, int);
			if (field_width < 0) {
				field_width = -field_width;
				flags |= LEFT;
			}
		}

		/* get the precision */
		precision = -1;
		if (*fmt == '.') {
			++fmt;
			if (is_digit(*fmt))
				precision = skip_atoi(&fmt);
			else if (*fmt == '*') {
				/* it's the next argument */
				precision = va_arg(args, int);
			}
			if (precision < 0)
				precision = 0;
		}

		/* get the conversion qualifier */
		//qualifier = -1;
		if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {
			//qualifier = *fmt;
			++fmt;
		}

		switch (*fmt) {
		case 'c':
			if (!(flags & LEFT))
				while (--field_width > 0)
					*str++ = ' ';
			*str++ = (unsigned char)va_arg(args, int);
			while (--field_width > 0)
				*str++ = ' ';
			break;

		case 's':
			s = va_arg(args, char *);
			len = strlen(s);
			if (precision < 0)
				precision = len;
			else if (len > precision)
				len = precision;

			if (!(flags & LEFT))
				while (len < field_width--)
					*str++ = ' ';
			for (i = 0; i < len; ++i)
				*str++ = *s++;
			while (len < field_width--)
				*str++ = ' ';
			break;

		case 'o':
			str = number(str, va_arg(args, unsigned long), 8,
				field_width, precision, flags);
			break;

		case 'p':
			if (field_width == -1) {
				field_width = 8;
				flags |= ZEROPAD;
			}
			str = number(str,
				(unsigned long)va_arg(args, void *), 16,
				field_width, precision, flags);
			break;

		case 'x':
			flags |= SMALL;
		case 'X':
			str = number(str, va_arg(args, unsigned long), 16,
				field_width, precision, flags);
			break;

		case 'd':
		case 'i':
			flags |= SIGN;
		case 'u':
			str = number(str, va_arg(args, unsigned long), 10,
				field_width, precision, flags);
			break;

		case 'n':
			ip = va_arg(args, int *);
			*ip = (str - buf);
			break;

		default:
			if (*fmt != '%')
				*str++ = '%';
			if (*fmt)
				*str++ = *fmt;
			else
				--fmt;
			break;
		}
	}
	*str = '\0';
	return str - buf;
}

void DebugPrint(const char * format, ...)
{
	char buffer[256];
	va_list args;
	va_start(args, format);
	vsprintf(buffer, format, args);
	printf(buffer);
	va_end(args);
}


static void print(const char* data, size_t data_length)
{
	for (size_t i = 0; i < data_length; i++)
		putchar((int)((const unsigned char*)data)[i]);
}

int printf(const char* format, ...)
{
	// Obtain the list of parameters.
	va_list parameters;
	va_start(parameters, format);

	size_t amount = 0;

	while (*format != '\0')
	{
		// Print normal characters.
		if (*format != '%')
		{
			print(format, sizeof(char));

			++amount;
			++format;
			continue;
		}

		// Found a format character '%', absorb it.
		++format;

		// Determine what format to print.
		if (*format == 'c')
		{
			// Character format.
			char c = (char)va_arg(parameters, int); // char promotes to int
			print(&c, sizeof(c));

			++amount;
			++format;
		}
		else if (*format == 's')
		{
			// String format.
			const char* data = va_arg(parameters, const char*);
			size_t data_length = strlen(data);
			print(data, data_length);

			amount += data_length;
			++format;
		}
		else if (*format == 'x')
		{
			// Hexadecimal format.
			const char* hexValues = "0123456789ABCDEF";
			const size_t data_length = 8;
			char hexResult[data_length];

			int val = va_arg(parameters, int);

			// Convert int to hex format.
			for (int i = data_length - 1; i >= 0; --i)
			{
				int hexIndex = val & 0xF;
				hexResult[i] = hexValues[hexIndex];
				val >>= 4;
			}

			print(hexResult, data_length);

			amount += data_length;
			++format;
		}
		else
		{
			// Backup the pointer and add print the character.
			--format;
			print(format, sizeof(char));
			++amount;
			++format;
		}
	}

	va_end(parameters);

	return amount;
}
